package repository

import (
	"context"
	"database/sql"
	"fmt"
	"strconv"
	"strings"

	"mall/common/enumx"
	"mall/common/modelx"
	"mall/service/product/model"

	"github.com/volatiletech/null/v8"
	"github.com/volatiletech/sqlboiler/v4/boil"
	"github.com/volatiletech/sqlboiler/v4/queries"
	"github.com/volatiletech/sqlboiler/v4/queries/qm"
	"github.com/zeromicro/go-zero/core/stores/cache"
)

type (
	// ProductRepository is an interface to be customized, add more methods here,
	// and implement the added methods in customProductRepository.
	ProductRepository interface {
		Conn() error
		FindOne(ctx context.Context, id int64, cols []string) (*model.ProductProduct, error) ///
		GetList(
			ctx context.Context,
			cols model.M,
			withCount int,
			filter map[string]map[string][]any,
		) ([]*model.ProductProduct, int64, error) ///
		Insert(
			ctx context.Context,
			product *model.ProductProduct,
			detail *model.ProductDetail,
			extra *model.ProductExtra,
		) error ///
		Update(
			ctx context.Context,
			product *model.ProductProduct,
			detail *model.ProductDetail,
			extra *model.ProductExtra,
		) (int64, error) ///
		UpdateBatch(ctx context.Context, ids []int64, cols model.M) (int64, error)
		RestoreBatch(ctx context.Context, ids []int64, cols model.M) (int64, error)
		ForceDeleteBatch(ctx context.Context, ids []int64) (int64, error)
		GetDetail(
			ctx context.Context,
			cols model.M,
			withCount int,
			filter map[string]map[string][]any,
		) ([]*model.ProductDetail, int64, error)
		GetEntryList(
			ctx context.Context,
			cols model.M,
			withCount int,
			filter map[string]map[string][]any,
		) ([]*model.ProductKnowledgeBaseRelationship, int64, error) ///
		GetAttributeList(
			ctx context.Context,
			cols model.M,
			withCount int,
			filter map[string]map[string][]any,
		) ([]*model.ProductAttribute, int64, error) ///
		GetAttributeOptionList(
			ctx context.Context,
			cols model.M,
			withCount int,
			filter map[string]map[string][]any,
		) ([]*model.ProductAttributeOption, int64, error)
		GetVariationList(
			ctx context.Context,
			cols model.M,
			withCount int,
			filter map[string]map[string][]any,
		) ([]*model.ProductVariation, int64, error)
		GetMeta(
			ctx context.Context,
			cols model.M,
			withCount int,
			filter map[string]map[string][]any,
		) ([]*model.ProductMetum, int64, error) ///
		GetVariationMeta(
			ctx context.Context,
			cols model.M,
			withCount int,
			filter map[string]map[string][]any,
		) ([]*model.ProductVariationMetum, int64, error) ///
		GetMedia(
			ctx context.Context,
			cols model.M,
			withCount int,
			filter map[string]map[string][]any,
		) ([]*model.ProductAttachmentRelationship, int64, error) ///
	}

	customProductRepository struct {
		cacheConf  cache.CacheConf
		dataSource string

		mysqlConn *sql.DB
	}
)

// NewProductRepository returns a repository for the database table.
func NewProductRepository(dataSource string, cacheConf cache.CacheConf) ProductRepository {
	return &customProductRepository{
		cacheConf:  cacheConf,
		dataSource: dataSource,
	}
}

func (r *customProductRepository) Conn() error {
	if r.mysqlConn == nil {
		mysqlConn, err := modelx.NewMysqlConn(r.dataSource)
		if err != nil {
			return err
		}

		r.mysqlConn = mysqlConn
	}

	return nil
}

func (r *customProductRepository) FindOne(
	ctx context.Context,
	id int64,
	cols []string,
) (product *model.ProductProduct, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	product, err = model.FindProductProduct(ctx, r.mysqlConn, uint64(id), cols...)
	if err == sql.ErrNoRows {
		return nil, model.ErrNotFound
	}

	return
}

func (r *customProductRepository) GetList(
	ctx context.Context,
	cols model.M,
	withCount int,
	filter map[string]map[string][]any,
) (list []*model.ProductProduct, count int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	var mods []qm.QueryMod
	var page int
	var pageSize int
	var offset int
	var limit int
	var selectCol string
	for k, v := range cols {
		switch k {
		case "page":
			if val, ok := v.(int64); ok && val > 1 {
				page = int(val)
			}
		case "pageSize":
			if val, ok := v.(int64); ok && val > 0 {
				pageSize = int(val)
			}
		case "offset":
			if val, ok := v.(int32); ok && val > 0 {
				offset = int(val)
			}
		case "limit":
			if val, ok := v.(int32); ok && val > 0 {
				limit = int(val)
			}
		case "orderBy":
			if val, ok := v.(string); ok && val != "" {
				mods = append(mods, qm.OrderBy(val))
			}
		case "select":
			if val, ok := v.(string); ok && val != "" {
				selectCol = val
			}
		case "asPreview":
			if val, ok := v.(string); ok && val == "" { // 不是预览，必须是已发布
				mods = append(mods, model.ProductProductWhere.Status.EQ(uint16(enumx.PostStatusPublish)))
			}
		case "search":
			if val, ok := v.(string); ok && val != "" { // 搜索
				likeVal := fmt.Sprintf("%%%s%%", val)
				mods = append(mods, qm.Where(fmt.Sprintf(
					"(%s LIKE ? OR %s LIKE ?)",
					model.ProductProductColumns.Title,
					model.ProductProductColumns.Slug), likeVal, likeVal))
			}
		}
	}

	if filter != nil {
		for key, opMap := range filter {
			switch key {
			case model.ProductProductColumns.ID:
				mods = append(mods, modelx.GetIntMods(model.ProductProductColumns.ID, opMap)...)
			case model.ProductProductColumns.ParentID:
				mods = append(mods, modelx.GetIntMods(model.ProductProductColumns.ParentID, opMap)...)
			case model.ProductProductColumns.Title:
				mods = append(mods, modelx.GetStringMods(model.ProductProductColumns.Title, opMap)...)
			case model.ProductProductColumns.Slug:
				mods = append(mods, modelx.GetStringMods(model.ProductProductColumns.Slug, opMap)...)
			case model.ProductProductColumns.Excerpt:
				mods = append(mods, modelx.GetStringMods(model.ProductProductColumns.Excerpt, opMap)...)
			case model.ProductProductColumns.Status:
				mods = append(mods, modelx.GetIntMods(model.ProductProductColumns.Status, opMap)...)
			case model.ProductProductColumns.VirtualType:
				mods = append(mods, modelx.GetIntMods(model.ProductProductColumns.VirtualType, opMap)...)
			}
		}
	}

	if withCount >= 0 {
		if withCount >= 2 {
			var nextCount int64
			var previousCount int64
			subMods := make([]qm.QueryMod, len(mods), len(mods)+3)
			copy(subMods, mods)
			subMods = append(subMods, qm.Limit(1))
			subMods = append(subMods, qm.Select(model.ProductProductColumns.ID))

			if withCount != 3 && offset >= 1 {
				subMods = append(subMods, qm.Offset(offset-1))
				previousList, previousErr := model.ProductProducts(subMods...).All(ctx, r.mysqlConn)
				if previousErr != nil {
					err = previousErr
					return
				}

				if len(previousList) > 0 {
					previousCount = 1
				}
			}
			if limit > 0 {
				subMods = append(subMods, qm.Offset(offset+limit))
				nextList, nextErr := model.ProductProducts(subMods...).All(ctx, r.mysqlConn)
				if nextErr != nil {
					err = nextErr
					return
				}

				if len(nextList) > 0 {
					nextCount = 1
				}
			}
			count = previousCount + nextCount*2
		} else {
			count, err = model.ProductProducts(mods...).Count(ctx, r.mysqlConn)
			if err != nil {
				return
			}

			if withCount == 0 {
				return
			}
		}
	}

	if pageSize > 0 {
		if page > 1 {
			mods = append(mods, qm.Offset(pageSize*(page-1)))
		}
		mods = append(mods, qm.Limit(pageSize))
	} else if limit > 0 || offset > 0 {
		if limit > 0 {
			mods = append(mods, qm.Limit(limit))
		}
		if offset > 0 {
			mods = append(mods, qm.Offset(offset))
		}
	}

	var columns []string
	if selectCol != "" {
		if selectCol != "," {
			columns = strings.Split(selectCol, ",")
		}
		columns = append(columns, model.ProductProductColumns.ID)
	}
	if len(columns) > 0 {
		mods = append(mods, qm.Select(columns...))
	}

	list, err = model.ProductProducts(mods...).All(ctx, r.mysqlConn)
	return
}

func (r *customProductRepository) Insert(
	ctx context.Context,
	product *model.ProductProduct,
	detail *model.ProductDetail,
	extra *model.ProductExtra,
) error {
	if err := r.Conn(); err != nil {
		return err
	}

	tx, err := r.mysqlConn.BeginTx(ctx, nil)
	if err != nil {
		return err
	}

	err = product.Insert(ctx, tx, boil.Infer())
	if err != nil {
		tx.Rollback()
		return err
	}

	pid := product.ID
	detail.ProductID = pid
	err = detail.Insert(ctx, tx, boil.Infer())
	if err != nil {
		tx.Rollback()
		return err
	}

	if len(extra.Meta) > 0 {
		for _, m := range extra.Meta {
			m.ProductID = pid
			err = m.Insert(ctx, tx, boil.Infer())
			if err != nil {
				tx.Rollback()
				return err
			}
		}
	}

	objectType := uint16(enumx.PostTypeProduct)
	if len(extra.CategoryIds) > 0 {
		for _, cid := range extra.CategoryIds {
			var relationShip model.ProductTermRelationship
			relationShip.ObjectID = pid
			relationShip.ObjectType = objectType
			relationShip.TermID = uint64(cid)
			err = relationShip.Insert(ctx, tx, boil.Infer())
			if err != nil {
				tx.Rollback()
				return err
			}
		}
	}
	if len(extra.TagIds) > 0 {
		for _, tid := range extra.TagIds {
			var relationShip model.ProductTermRelationship
			relationShip.ObjectID = pid
			relationShip.ObjectType = objectType
			relationShip.TermID = uint64(tid)
			err = relationShip.Insert(ctx, tx, boil.Infer())
			if err != nil {
				tx.Rollback()
				return err
			}
		}
	}

	if len(extra.Media) > 0 {
		for _, m := range extra.Media {
			m.ObjectID = pid
			m.ObjectType = objectType
			err = m.Insert(ctx, tx, boil.Infer())
			if err != nil {
				tx.Rollback()
				return err
			}
		}
	}

	if extra.RelationShip != nil {
		relationShip := extra.RelationShip
		relationShip.ProductID = pid
		err = relationShip.Insert(ctx, tx, boil.Infer())
		if err != nil {
			tx.Rollback()
			return err
		}
	}

	optionKeyMap := make(map[string]uint64)
	if len(extra.Attributes) > 0 {
		for _, item := range extra.Attributes {
			if len(item.Options) > 0 {
				attr := item.Attribute
				attr.ProductID = pid
				err = attr.Insert(ctx, tx, boil.Infer())
				if err != nil {
					tx.Rollback()
					return err
				}

				attrId := attr.ProductAttributeID
				for _, opt := range item.Options {
					tmpIdx := opt.Status
					if tmpIdx < 0 { // 是规格
						if key, exist := extra.OptionKeyMap[tmpIdx]; exist {
							opt.Status = int8(enumx.ObjectStatusAproved)
							opt.AttributeID = attrId
							err = opt.Insert(ctx, tx, boil.Infer())
							if err != nil {
								tx.Rollback()
								return err
							}

							optionKeyMap[key] = opt.OptionID
						}
					} else {
						opt.AttributeID = attrId
						err = opt.Insert(ctx, tx, boil.Infer())
						if err != nil {
							tx.Rollback()
							return err
						}
					}
				}
			}
		}
	}
	if len(extra.Variations) > 0 {
		for _, item := range extra.Variations {
			variation := item.Variation
			variation.ProductID = pid
			err = variation.Insert(ctx, tx, boil.Infer())
			if err != nil {
				tx.Rollback()
				return err
			}

			variationId := variation.ProductVariationID
			if len(item.Meta) > 0 {
				for _, metum := range item.Meta {
					if metum.MetaKey == "_sku_key" {
						val := metum.MetaValue.String
						keys := strings.Split(val, "_-_")
						var newVals []string
						if len(keys) > 0 {
							for _, key := range keys {
								if optId, exist := optionKeyMap[key]; exist {
									newVals = append(newVals, strconv.FormatInt(int64(optId), 10))
								}
							}
						}
						metum.MetaValue = null.StringFrom(strings.Join(newVals, ","))
					}
					metum.ProductID = variationId
					err = metum.Insert(ctx, tx, boil.Infer())
					if err != nil {
						tx.Rollback()
						return err
					}
				}
			}
		}
	}

	tx.Commit()
	return nil
}

func (r *customProductRepository) Update(
	ctx context.Context,
	product *model.ProductProduct,
	detail *model.ProductDetail,
	extra *model.ProductExtra,
) (int64, error) {
	if err := r.Conn(); err != nil {
		return 0, err
	}

	tx, err := r.mysqlConn.BeginTx(ctx, nil)
	if err != nil {
		return 0, err
	}

	productId, err := product.Update(ctx, tx, boil.Blacklist(
		model.ProductProductColumns.ParentID,
		model.ProductProductColumns.ProductAuthorID,
	))
	if err != nil {
		tx.Rollback()
		return 0, err
	}

	pid := product.ID
	if detail != nil {
		detail.ProductID = pid
		_, err = detail.Update(ctx, tx, boil.Blacklist(
			model.ProductDetailColumns.DetailID,
			model.ProductDetailColumns.ProductID,
		))
		if err != nil {
			tx.Rollback()
			return 0, err
		}
	}

	if extra != nil {
		if len(extra.Meta) > 0 {
			for _, m := range extra.Meta {
				if m.MetaID > 0 {
					_, err = m.Update(ctx, tx, boil.Blacklist(
						model.ProductMetumColumns.MetaID,
						model.ProductMetumColumns.ProductID,
					))
				} else {
					m.ProductID = pid
					err = m.Insert(ctx, tx, boil.Infer())
				}
				if err != nil {
					tx.Rollback()
					return 0, err
				}
			}
		}

		objectType := uint16(enumx.PostTypeProduct)
		if len(extra.Terms) > 0 {
			for _, t := range extra.Terms {
				if t.TermRelationshipID > 0 {
					_, err = t.Update(ctx, tx, boil.Blacklist(
						model.ProductTermRelationshipColumns.TermRelationshipID,
						model.ProductTermRelationshipColumns.ObjectID,
						model.ProductTermRelationshipColumns.ObjectType,
						model.ProductTermRelationshipColumns.TermID,
					))
					if err != nil {
						tx.Rollback()
						return 0, err
					}
				}
			}
		}
		if len(extra.CategoryIds) > 0 {
			for _, cid := range extra.CategoryIds {
				var relationShip model.ProductTermRelationship
				relationShip.ObjectID = pid
				relationShip.ObjectType = objectType
				relationShip.TermID = uint64(cid)
				err = relationShip.Insert(ctx, tx, boil.Infer())
				if err != nil {
					tx.Rollback()
					return 0, err
				}
			}
		}
		if len(extra.TagIds) > 0 {
			for _, tid := range extra.TagIds {
				var relationShip model.ProductTermRelationship
				relationShip.ObjectID = pid
				relationShip.ObjectType = objectType
				relationShip.TermID = uint64(tid)
				err = relationShip.Insert(ctx, tx, boil.Infer())
				if err != nil {
					tx.Rollback()
					return 0, err
				}
			}
		}
		if len(extra.Media) > 0 {
			for _, m := range extra.Media {
				if m.AttachmentRelationshipID > 0 {
					_, err = m.Update(ctx, tx, boil.Blacklist(
						model.ProductAttachmentRelationshipColumns.AttachmentRelationshipID,
						model.ProductAttachmentRelationshipColumns.ObjectID,
						model.ProductAttachmentRelationshipColumns.ObjectType,
						model.ProductAttachmentRelationshipColumns.AttachmentID,
					))
				} else {
					m.ObjectID = pid
					m.ObjectType = objectType
					err = m.Insert(ctx, tx, boil.Infer())
				}
				if err != nil {
					tx.Rollback()
					return 0, err
				}
			}
		}

		if extra.RelationShip != nil {
			relationShip := extra.RelationShip
			if relationShip.ProductRelationshipID > 0 {
				_, err = relationShip.Update(ctx, tx, boil.Blacklist(
					model.ProductKnowledgeBaseRelationshipColumns.ProductRelationshipID,
					model.ProductKnowledgeBaseRelationshipColumns.ProductID,
				))
			} else {
				relationShip.ProductID = pid
				err = relationShip.Insert(ctx, tx, boil.Infer())
			}
			if err != nil {
				tx.Rollback()
				return 0, err
			}
		}

		optionKeyMap := make(map[string]uint64)
		if len(extra.Attributes) > 0 {
			for _, item := range extra.Attributes {
				if len(item.Options) > 0 {
					attr := item.Attribute
					if attr.ProductAttributeID > 0 {
						_, err = attr.Update(ctx, tx, boil.Blacklist(
							model.ProductAttributeColumns.ProductAttributeID,
							model.ProductAttributeColumns.ProductID,
						))
					} else {
						attr.ProductID = pid
						err = attr.Insert(ctx, tx, boil.Infer())
					}
					if err != nil {
						tx.Rollback()
						return 0, err
					}
					attrId := attr.ProductAttributeID
					for _, opt := range item.Options {
						tmpIdx := opt.Status
						if tmpIdx < 0 { // 是规格
							if key, exist := extra.OptionKeyMap[tmpIdx]; exist {
								if opt.OptionID > 0 {
									opt.Status = int8(enumx.ObjectStatusAproved)
									_, err = opt.Update(ctx, tx, boil.Blacklist(
										model.ProductAttributeOptionColumns.OptionID,
										model.ProductAttributeOptionColumns.AttributeID,
									))
								} else {
									opt.AttributeID = attrId
									opt.Status = int8(enumx.ObjectStatusAproved)
									err = opt.Insert(ctx, tx, boil.Infer())
								}
								if err != nil {
									tx.Rollback()
									return 0, err
								}

								optionKeyMap[key] = opt.OptionID
							}
						} else {
							if opt.OptionID > 0 {
								_, err = opt.Update(ctx, tx, boil.Blacklist(
									model.ProductAttributeOptionColumns.OptionID,
									model.ProductAttributeOptionColumns.AttributeID,
								))
							} else {
								opt.AttributeID = attrId
								err = opt.Insert(ctx, tx, boil.Infer())
							}

							if err != nil {
								tx.Rollback()
								return 0, err
							}
						}
					}
				}
			}
		}
		if len(extra.Variations) > 0 {
			for _, item := range extra.Variations {
				variation := item.Variation
				if variation.ProductVariationID > 0 {
					_, err = variation.Update(ctx, tx, boil.Blacklist(
						model.ProductVariationColumns.ProductVariationID,
						model.ProductVariationColumns.ProductID,
					))
				} else {
					variation.ProductID = pid
					err = variation.Insert(ctx, tx, boil.Infer())
				}
				if err != nil {
					tx.Rollback()
					return 0, err
				}

				variationId := variation.ProductVariationID
				if len(item.Meta) > 0 {
					for _, metum := range item.Meta {
						if metum.MetaKey == "_sku_key" {
							val := metum.MetaValue.String
							keys := strings.Split(val, "_-_")
							var newVals []string
							if len(keys) > 0 {
								for _, key := range keys {
									if optId, exist := optionKeyMap[key]; exist {
										newVals = append(newVals, strconv.FormatInt(int64(optId), 10))
									}
								}
							}
							metum.MetaValue = null.StringFrom(strings.Join(newVals, ","))
						}

						if metum.MetaID > 0 {
							_, err = metum.Update(ctx, tx, boil.Blacklist(
								model.ProductVariationMetumColumns.MetaID,
								model.ProductVariationMetumColumns.ProductID,
							))
						} else {
							metum.ProductID = variationId
							err = metum.Insert(ctx, tx, boil.Infer())
						}
						if err != nil {
							tx.Rollback()
							return 0, err
						}
					}
				}
			}
		}

		if extra.DelRelationId > 0 {
			var mods []qm.QueryMod
			mods = append(mods, model.ProductKnowledgeBaseRelationshipWhere.ProductRelationshipID.EQ(extra.DelRelationId))
			_, err := model.ProductKnowledgeBaseRelationships(mods...).DeleteAll(ctx, tx)
			if err != nil {
				tx.Rollback()
				return 0, err
			}
		}
		if len(extra.DelMetaIds) > 0 {
			ids := make([]any, 0, len(extra.DelMetaIds))
			for _, id := range extra.DelMetaIds {
				ids = append(ids, id)
			}
			var mods []qm.QueryMod
			mods = append(mods, qm.WhereIn(fmt.Sprintf("%s IN ?", model.ProductMetumColumns.MetaID), ids...))
			_, err := model.ProductMeta(mods...).DeleteAll(ctx, tx)
			if err != nil {
				tx.Rollback()
				return 0, err
			}
		}
		if len(extra.DelVariationMetaIds) > 0 {
			ids := make([]any, 0, len(extra.DelVariationMetaIds))
			for _, id := range extra.DelVariationMetaIds {
				ids = append(ids, id)
			}
			var mods []qm.QueryMod
			mods = append(mods, qm.WhereIn(fmt.Sprintf("%s IN ?", model.ProductVariationMetumColumns.MetaID), ids...))
			_, err := model.ProductVariationMeta(mods...).DeleteAll(ctx, tx)
			if err != nil {
				tx.Rollback()
				return 0, err
			}
		}
		if len(extra.DelTermIds) > 0 {
			ids := make([]any, 0, len(extra.DelTermIds))
			for _, id := range extra.DelTermIds {
				ids = append(ids, id)
			}
			var mods []qm.QueryMod
			mods = append(mods, qm.WhereIn(fmt.Sprintf("%s IN ?", model.ProductTermRelationshipColumns.TermRelationshipID), ids...))
			_, err := model.ProductTermRelationships(mods...).DeleteAll(ctx, tx)
			if err != nil {
				tx.Rollback()
				return 0, err
			}
		}
		if len(extra.DelMediaIds) > 0 {
			ids := make([]any, 0, len(extra.DelMediaIds))
			for _, id := range extra.DelMediaIds {
				ids = append(ids, id)
			}
			var mods []qm.QueryMod
			mods = append(mods, qm.WhereIn(fmt.Sprintf("%s IN ?", model.ProductAttachmentRelationshipColumns.AttachmentRelationshipID), ids...))
			_, err := model.ProductAttachmentRelationships(mods...).DeleteAll(ctx, tx)
			if err != nil {
				tx.Rollback()
				return 0, err
			}
		}
		if len(extra.DelAttrOptIds) > 0 {
			ids := make([]any, 0, len(extra.DelAttrOptIds))
			for _, id := range extra.DelAttrOptIds {
				ids = append(ids, id)
			}
			var mods []qm.QueryMod
			mods = append(mods, qm.WhereIn(fmt.Sprintf("%s IN ?", model.ProductAttributeOptionColumns.OptionID), ids...))
			_, err := model.ProductAttributeOptions(mods...).DeleteAll(ctx, tx)
			if err != nil {
				tx.Rollback()
				return 0, err
			}
		}
		if len(extra.DelAttributeIds) > 0 {
			ids := make([]any, 0, len(extra.DelAttributeIds))
			for _, id := range extra.DelAttributeIds {
				ids = append(ids, id)
			}
			var mods []qm.QueryMod
			mods = append(mods, qm.WhereIn(fmt.Sprintf("%s IN ?", model.ProductAttributeColumns.ProductAttributeID), ids...))
			_, err := model.ProductAttributes(mods...).DeleteAll(ctx, tx)
			if err != nil {
				tx.Rollback()
				return 0, err
			}
		}
		if len(extra.DelVariationIds) > 0 {
			ids := make([]any, 0, len(extra.DelVariationIds))
			for _, id := range extra.DelVariationIds {
				ids = append(ids, id)
			}
			var mods []qm.QueryMod
			mods = append(mods, qm.WhereIn(fmt.Sprintf("%s IN ?", model.ProductVariationColumns.ProductVariationID), ids...))
			_, err := model.ProductVariations(mods...).UpdateAll(ctx, tx, model.M{
				model.ProductVariationColumns.Status: uint8(enumx.ObjectStatusDeleted),
			})
			if err != nil {
				tx.Rollback()
				return 0, err
			}
		}
	}

	tx.Commit()
	return productId, nil
}

func (r *customProductRepository) UpdateBatch(ctx context.Context, ids []int64, cols model.M) (rowsAff int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	var mods []qm.QueryMod
	if len(ids) > 0 {
		values := make([]any, 0, len(ids))
		for _, id := range ids {
			values = append(values, uint64(id))
		}

		mods = append(mods, qm.WhereIn(fmt.Sprintf("%s IN ?", model.ProductProductColumns.ID), values...))
	}

	rowsAff, err = model.ProductProducts(mods...).UpdateAll(ctx, r.mysqlConn, cols)
	return
}

func (r *customProductRepository) RestoreBatch(ctx context.Context, ids []int64, cols model.M) (rowsAff int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	if len(ids) > 0 {
		values := make([]string, 0, len(ids))
		for _, id := range ids {
			values = append(values, strconv.FormatInt(id, 10))
		}

		var info RandomInfo
		err = queries.Raw(fmt.Sprintf(
			`update %s set %s=%s where %s in (%s)`,
			model.TableNames.ProductProducts,
			model.ProductProductColumns.Status,
			model.ProductProductColumns.DelStatus,
			model.ProductProductColumns.ID,
			strings.Join(values, ","),
		)).Bind(ctx, r.mysqlConn, &info)
	}
	return
}

func (r *customProductRepository) ForceDeleteBatch(ctx context.Context, ids []int64) (rowsAff int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	if len(ids) > 0 {
		var mods []qm.QueryMod
		values := make([]any, 0, len(ids))
		for _, id := range ids {
			values = append(values, uint64(id))
		}
		mods = append(mods, qm.WhereIn(fmt.Sprintf("%s IN ?", model.ProductProductColumns.ID), values...))
		rowsAff, err = model.ProductProducts(mods...).DeleteAll(ctx, r.mysqlConn)
	}

	return
}

func (r *customProductRepository) GetDetail(
	ctx context.Context,
	cols model.M,
	withCount int,
	filter map[string]map[string][]any,
) (list []*model.ProductDetail, count int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	var mods []qm.QueryMod
	var page int
	var pageSize int
	var selectCol string
	for k, v := range cols {
		switch k {
		case "page":
			if val, ok := v.(int64); ok && val > 1 {
				page = int(val)
			}
		case "pageSize":
			if val, ok := v.(int64); ok && val > 0 {
				pageSize = int(val)
			}
		case "orderBy":
			if val, ok := v.(string); ok && val != "" {
				mods = append(mods, qm.OrderBy(val))
			}
		case "select":
			if val, ok := v.(string); ok && val != "" {
				selectCol = val
			}
		}
	}

	if filter != nil {
		for key, opMap := range filter {
			switch key {
			case model.ProductDetailColumns.DetailID:
				mods = append(mods, modelx.GetIntMods(model.ProductDetailColumns.DetailID, opMap)...)
			case model.ProductDetailColumns.ProductID:
				mods = append(mods, modelx.GetIntMods(model.ProductDetailColumns.ProductID, opMap)...)
			}
		}
	}

	if withCount >= 0 {
		count, err = model.ProductDetails(mods...).Count(ctx, r.mysqlConn)
		if err != nil {
			return
		}

		if withCount == 0 {
			return
		}
	}

	if pageSize > 0 {
		if page > 1 {
			mods = append(mods, qm.Offset(pageSize*(page-1)))
		}
		mods = append(mods, qm.Limit(pageSize))
	}

	var columns []string
	if selectCol != "" {
		if selectCol != "," {
			columns = strings.Split(selectCol, ",")
		}
		columns = append(columns, model.ProductDetailColumns.DetailID)
		columns = append(columns, model.ProductDetailColumns.ProductID)
	}
	if len(columns) > 0 {
		mods = append(mods, qm.Select(columns...))
	}

	list, err = model.ProductDetails(mods...).All(ctx, r.mysqlConn)
	return
}

func (r *customProductRepository) GetEntryList(
	ctx context.Context,
	cols model.M,
	withCount int,
	filter map[string]map[string][]any,
) (list []*model.ProductKnowledgeBaseRelationship, count int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	var mods []qm.QueryMod
	var page int
	var pageSize int
	var selectCol string
	for k, v := range cols {
		switch k {
		case "page":
			if val, ok := v.(int64); ok && val > 1 {
				page = int(val)
			}
		case "pageSize":
			if val, ok := v.(int64); ok && val > 0 {
				pageSize = int(val)
			}
		case "orderBy":
			if val, ok := v.(string); ok && val != "" {
				mods = append(mods, qm.OrderBy(val))
			}
		case "select":
			if val, ok := v.(string); ok && val != "" {
				selectCol = val
			}
		}
	}

	if filter != nil {
		for key, opMap := range filter {
			switch key {
			case model.ProductKnowledgeBaseRelationshipColumns.ProductRelationshipID:
				mods = append(mods, modelx.GetIntMods(model.ProductKnowledgeBaseRelationshipColumns.ProductRelationshipID, opMap)...)
			case model.ProductKnowledgeBaseRelationshipColumns.ProductID:
				mods = append(mods, modelx.GetIntMods(model.ProductKnowledgeBaseRelationshipColumns.ProductID, opMap)...)
			case model.ProductKnowledgeBaseRelationshipColumns.ToEntryID:
				mods = append(mods, modelx.GetIntMods(model.ProductKnowledgeBaseRelationshipColumns.ToEntryID, opMap)...)
			case model.ProductKnowledgeBaseRelationshipColumns.ToEntryGroupID:
				mods = append(mods, modelx.GetIntMods(model.ProductKnowledgeBaseRelationshipColumns.ToEntryGroupID, opMap)...)
			}
		}
	}

	if withCount >= 0 {
		count, err = model.ProductKnowledgeBaseRelationships(mods...).Count(ctx, r.mysqlConn)
		if err != nil {
			return
		}

		if withCount == 0 {
			return
		}
	}

	if pageSize > 0 {
		if page > 1 {
			mods = append(mods, qm.Offset(pageSize*(page-1)))
		}
		mods = append(mods, qm.Limit(pageSize))
	}

	var columns []string
	if selectCol != "" {
		if selectCol != "," {
			columns = strings.Split(selectCol, ",")
		}
		columns = append(columns, model.ProductKnowledgeBaseRelationshipColumns.ProductRelationshipID)
		columns = append(columns, model.ProductKnowledgeBaseRelationshipColumns.ProductID)
	}
	if len(columns) > 0 {
		mods = append(mods, qm.Select(columns...))
	}

	list, err = model.ProductKnowledgeBaseRelationships(mods...).All(ctx, r.mysqlConn)
	return
}

func (r *customProductRepository) GetAttributeList(
	ctx context.Context,
	cols model.M,
	withCount int,
	filter map[string]map[string][]any,
) (list []*model.ProductAttribute, count int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	var mods []qm.QueryMod
	var page int
	var pageSize int
	var selectCol string
	for k, v := range cols {
		switch k {
		case "page":
			if val, ok := v.(int64); ok && val > 1 {
				page = int(val)
			}
		case "pageSize":
			if val, ok := v.(int64); ok && val > 0 {
				pageSize = int(val)
			}
		case "orderBy":
			if val, ok := v.(string); ok && val != "" {
				mods = append(mods, qm.OrderBy(val))
			}
		case "select":
			if val, ok := v.(string); ok && val != "" {
				selectCol = val
			}
		}
	}

	if filter != nil {
		for key, opMap := range filter {
			switch key {
			case model.ProductAttributeColumns.ProductAttributeID:
				mods = append(mods, modelx.GetIntMods(model.ProductAttributeColumns.ProductAttributeID, opMap)...)
			case model.ProductAttributeColumns.ProductID:
				mods = append(mods, modelx.GetIntMods(model.ProductAttributeColumns.ProductID, opMap)...)
			case model.ProductAttributeColumns.TermID:
				mods = append(mods, modelx.GetIntMods(model.ProductAttributeColumns.TermID, opMap)...)
			case model.ProductAttributeColumns.Taxonomy:
				mods = append(mods, modelx.GetIntMods(model.ProductAttributeColumns.Taxonomy, opMap)...)
			case model.ProductAttributeColumns.IsVariationAttribute:
				mods = append(mods, modelx.GetBooleanMods(model.ProductAttributeColumns.IsVariationAttribute, opMap)...)
			case model.ProductAttributeColumns.Name:
				mods = append(mods, modelx.GetStringMods(model.ProductAttributeColumns.Name, opMap)...)
			case model.ProductAttributeColumns.Slug:
				mods = append(mods, modelx.GetStringMods(model.ProductAttributeColumns.Slug, opMap)...)
			case model.ProductAttributeColumns.HasImage:
				mods = append(mods, modelx.GetBooleanMods(model.ProductAttributeColumns.HasImage, opMap)...)
			}
		}
	}

	if withCount >= 0 {
		count, err = model.ProductAttributes(mods...).Count(ctx, r.mysqlConn)
		if err != nil {
			return
		}

		if withCount == 0 {
			return
		}
	}

	if pageSize > 0 {
		if page > 1 {
			mods = append(mods, qm.Offset(pageSize*(page-1)))
		}
		mods = append(mods, qm.Limit(pageSize))
	}

	var columns []string
	if selectCol != "" {
		if selectCol != "," {
			columns = strings.Split(selectCol, ",")
		}
		columns = append(columns, model.ProductAttributeColumns.ProductAttributeID)
		columns = append(columns, model.ProductAttributeColumns.ProductID)
	}
	if len(columns) > 0 {
		mods = append(mods, qm.Select(columns...))
	}

	list, err = model.ProductAttributes(mods...).All(ctx, r.mysqlConn)
	return
}

func (r *customProductRepository) GetAttributeOptionList(
	ctx context.Context,
	cols model.M,
	withCount int,
	filter map[string]map[string][]any,
) (list []*model.ProductAttributeOption, count int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	var mods []qm.QueryMod
	var page int
	var pageSize int
	var selectCol string
	for k, v := range cols {
		switch k {
		case "page":
			if val, ok := v.(int64); ok && val > 1 {
				page = int(val)
			}
		case "pageSize":
			if val, ok := v.(int64); ok && val > 0 {
				pageSize = int(val)
			}
		case "orderBy":
			if val, ok := v.(string); ok && val != "" {
				mods = append(mods, qm.OrderBy(val))
			}
		case "select":
			if val, ok := v.(string); ok && val != "" {
				selectCol = val
			}
		}
	}

	if filter != nil {
		for key, opMap := range filter {
			switch key {
			case model.ProductAttributeOptionColumns.OptionID:
				mods = append(mods, modelx.GetIntMods(model.ProductAttributeOptionColumns.OptionID, opMap)...)
			case model.ProductAttributeOptionColumns.AttributeID:
				mods = append(mods, modelx.GetIntMods(model.ProductAttributeOptionColumns.AttributeID, opMap)...)
			case model.ProductAttributeOptionColumns.TaxonomyOptionID:
				mods = append(mods, modelx.GetIntMods(model.ProductAttributeOptionColumns.TaxonomyOptionID, opMap)...)
			case model.ProductAttributeOptionColumns.Status:
				mods = append(mods, modelx.GetBooleanMods(model.ProductAttributeOptionColumns.Status, opMap)...)
			case model.ProductAttributeOptionColumns.Name:
				mods = append(mods, modelx.GetStringMods(model.ProductAttributeOptionColumns.Name, opMap)...)
			case model.ProductAttributeOptionColumns.Slug:
				mods = append(mods, modelx.GetStringMods(model.ProductAttributeOptionColumns.Slug, opMap)...)
			}
		}
	}

	if withCount >= 0 {
		count, err = model.ProductAttributeOptions(mods...).Count(ctx, r.mysqlConn)
		if err != nil {
			return
		}

		if withCount == 0 {
			return
		}
	}

	if pageSize > 0 {
		if page > 1 {
			mods = append(mods, qm.Offset(pageSize*(page-1)))
		}
		mods = append(mods, qm.Limit(pageSize))
	}

	var columns []string
	if selectCol != "" {
		if selectCol != "," {
			columns = strings.Split(selectCol, ",")
		}
		columns = append(columns, model.ProductAttributeOptionColumns.OptionID)
		columns = append(columns, model.ProductAttributeOptionColumns.AttributeID)
	}
	if len(columns) > 0 {
		mods = append(mods, qm.Select(columns...))
	}

	list, err = model.ProductAttributeOptions(mods...).All(ctx, r.mysqlConn)
	return
}

func (r *customProductRepository) GetVariationList(
	ctx context.Context,
	cols model.M,
	withCount int,
	filter map[string]map[string][]any,
) (list []*model.ProductVariation, count int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	var mods []qm.QueryMod
	var page int
	var pageSize int
	var selectCol string
	for k, v := range cols {
		switch k {
		case "page":
			if val, ok := v.(int64); ok && val > 1 {
				page = int(val)
			}
		case "pageSize":
			if val, ok := v.(int64); ok && val > 0 {
				pageSize = int(val)
			}
		case "orderBy":
			if val, ok := v.(string); ok && val != "" {
				mods = append(mods, qm.OrderBy(val))
			}
		case "select":
			if val, ok := v.(string); ok && val != "" {
				selectCol = val
			}
		}
	}

	if filter != nil {
		for key, opMap := range filter {
			switch key {
			case model.ProductVariationColumns.ProductVariationID:
				mods = append(mods, modelx.GetIntMods(model.ProductVariationColumns.ProductVariationID, opMap)...)
			case model.ProductVariationColumns.ProductID:
				mods = append(mods, modelx.GetIntMods(model.ProductVariationColumns.ProductID, opMap)...)
			case model.ProductVariationColumns.GemPrice:
				mods = append(mods, modelx.GetIntMods(model.ProductVariationColumns.GemPrice, opMap)...)
			case model.ProductVariationColumns.RetailPrice:
				mods = append(mods, modelx.GetIntMods(model.ProductVariationColumns.RetailPrice, opMap)...)
			case model.ProductVariationColumns.SkuCode:
				mods = append(mods, modelx.GetStringMods(model.ProductVariationColumns.SkuCode, opMap)...)
			case model.ProductVariationColumns.SkuType:
				mods = append(mods, modelx.GetIntMods(model.ProductVariationColumns.SkuType, opMap)...)
			case model.ProductVariationColumns.Status:
				mods = append(mods, modelx.GetIntMods(model.ProductVariationColumns.Status, opMap)...)
			}
		}
	}

	if withCount >= 0 {
		count, err = model.ProductVariations(mods...).Count(ctx, r.mysqlConn)
		if err != nil {
			return
		}

		if withCount == 0 {
			return
		}
	}

	if pageSize > 0 {
		if page > 1 {
			mods = append(mods, qm.Offset(pageSize*(page-1)))
		}
		mods = append(mods, qm.Limit(pageSize))
	}

	var columns []string
	if selectCol != "" {
		if selectCol != "," {
			columns = strings.Split(selectCol, ",")
		}
		columns = append(columns, model.ProductVariationColumns.ProductVariationID)
		columns = append(columns, model.ProductVariationColumns.ProductID)
	}
	if len(columns) > 0 {
		mods = append(mods, qm.Select(columns...))
	}

	list, err = model.ProductVariations(mods...).All(ctx, r.mysqlConn)
	return
}

func (r *customProductRepository) GetMeta(
	ctx context.Context,
	cols model.M,
	withCount int,
	filter map[string]map[string][]any,
) (list []*model.ProductMetum, count int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	var mods []qm.QueryMod
	var page int
	var pageSize int
	var selectCol string
	for k, v := range cols {
		switch k {
		case "page":
			if val, ok := v.(int64); ok && val > 1 {
				page = int(val)
			}
		case "pageSize":
			if val, ok := v.(int64); ok && val > 0 {
				pageSize = int(val)
			}
		case "orderBy":
			if val, ok := v.(string); ok && val != "" {
				mods = append(mods, qm.OrderBy(val))
			}
		case "select":
			if val, ok := v.(string); ok && val != "" {
				selectCol = val
			}
		}
	}

	if filter != nil {
		for key, opMap := range filter {
			switch key {
			case model.ProductMetumColumns.MetaID:
				mods = append(mods, modelx.GetIntMods(model.ProductMetumColumns.MetaID, opMap)...)
			case model.ProductMetumColumns.ProductID:
				mods = append(mods, modelx.GetIntMods(model.ProductMetumColumns.ProductID, opMap)...)
			case model.ProductMetumColumns.MetaKey:
				mods = append(mods, modelx.GetStringMods(model.ProductMetumColumns.MetaKey, opMap)...)
			}
		}
	}

	if withCount >= 0 {
		count, err = model.ProductMeta(mods...).Count(ctx, r.mysqlConn)
		if err != nil {
			return
		}

		if withCount == 0 {
			return
		}
	}

	if pageSize > 0 {
		if page > 1 {
			mods = append(mods, qm.Offset(pageSize*(page-1)))
		}
		mods = append(mods, qm.Limit(pageSize))
	}

	var columns []string
	if selectCol != "" {
		if selectCol != "," {
			columns = strings.Split(selectCol, ",")
		}
		columns = append(columns, model.ProductMetumColumns.MetaID)
		columns = append(columns, model.ProductMetumColumns.ProductID)
	}
	if len(columns) > 0 {
		mods = append(mods, qm.Select(columns...))
	}

	list, err = model.ProductMeta(mods...).All(ctx, r.mysqlConn)
	return
}

func (r *customProductRepository) GetVariationMeta(
	ctx context.Context,
	cols model.M,
	withCount int,
	filter map[string]map[string][]any,
) (list []*model.ProductVariationMetum, count int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	var mods []qm.QueryMod
	var page int
	var pageSize int
	var selectCol string
	for k, v := range cols {
		switch k {
		case "page":
			if val, ok := v.(int64); ok && val > 1 {
				page = int(val)
			}
		case "pageSize":
			if val, ok := v.(int64); ok && val > 0 {
				pageSize = int(val)
			}
		case "orderBy":
			if val, ok := v.(string); ok && val != "" {
				mods = append(mods, qm.OrderBy(val))
			}
		case "select":
			if val, ok := v.(string); ok && val != "" {
				selectCol = val
			}
		}
	}

	if filter != nil {
		for key, opMap := range filter {
			switch key {
			case model.ProductVariationMetumColumns.MetaID:
				mods = append(mods, modelx.GetIntMods(model.ProductVariationMetumColumns.MetaID, opMap)...)
			case model.ProductVariationMetumColumns.ProductID:
				mods = append(mods, modelx.GetIntMods(model.ProductVariationMetumColumns.ProductID, opMap)...)
			case model.ProductVariationMetumColumns.MetaKey:
				mods = append(mods, modelx.GetStringMods(model.ProductVariationMetumColumns.MetaKey, opMap)...)
			}
		}
	}

	if withCount >= 0 {
		count, err = model.ProductVariationMeta(mods...).Count(ctx, r.mysqlConn)
		if err != nil {
			return
		}

		if withCount == 0 {
			return
		}
	}

	if pageSize > 0 {
		if page > 1 {
			mods = append(mods, qm.Offset(pageSize*(page-1)))
		}
		mods = append(mods, qm.Limit(pageSize))
	}

	var columns []string
	if selectCol != "" {
		if selectCol != "," {
			columns = strings.Split(selectCol, ",")
		}
		columns = append(columns, model.ProductVariationMetumColumns.MetaID)
		columns = append(columns, model.ProductVariationMetumColumns.ProductID)
	}
	if len(columns) > 0 {
		mods = append(mods, qm.Select(columns...))
	}

	list, err = model.ProductVariationMeta(mods...).All(ctx, r.mysqlConn)
	return
}

func (r *customProductRepository) GetMedia(
	ctx context.Context,
	cols model.M,
	withCount int,
	filter map[string]map[string][]any,
) (list []*model.ProductAttachmentRelationship, count int64, err error) {
	if err = r.Conn(); err != nil {
		return
	}

	var mods []qm.QueryMod
	var page int
	var pageSize int
	var selectCol string
	for k, v := range cols {
		switch k {
		case "page":
			if val, ok := v.(int64); ok && val > 1 {
				page = int(val)
			}
		case "pageSize":
			if val, ok := v.(int64); ok && val > 0 {
				pageSize = int(val)
			}
		case "orderBy":
			if val, ok := v.(string); ok && val != "" {
				mods = append(mods, qm.OrderBy(val))
			}
		case "select":
			if val, ok := v.(string); ok && val != "" {
				selectCol = val
			}
		}
	}

	if filter != nil {
		for key, opMap := range filter {
			switch key {
			case model.ProductAttachmentRelationshipColumns.AttachmentRelationshipID:
				mods = append(mods, modelx.GetIntMods(model.ProductAttachmentRelationshipColumns.AttachmentRelationshipID, opMap)...)
			case model.ProductAttachmentRelationshipColumns.ObjectID:
				mods = append(mods, modelx.GetIntMods(model.ProductAttachmentRelationshipColumns.ObjectID, opMap)...)
			case model.ProductAttachmentRelationshipColumns.ObjectType:
				mods = append(mods, modelx.GetIntMods(model.ProductAttachmentRelationshipColumns.ObjectType, opMap)...)
			case model.ProductAttachmentRelationshipColumns.AttachmentID:
				mods = append(mods, modelx.GetIntMods(model.ProductAttachmentRelationshipColumns.AttachmentID, opMap)...)
			case model.ProductAttachmentRelationshipColumns.AttachmentThumbnail:
				mods = append(mods, modelx.GetIntMods(model.ProductAttachmentRelationshipColumns.AttachmentThumbnail, opMap)...)
			case model.ProductAttachmentRelationshipColumns.AttachmentOrder:
				mods = append(mods, modelx.GetIntMods(model.ProductAttachmentRelationshipColumns.AttachmentOrder, opMap)...)
			}
		}
	}

	if withCount >= 0 {
		count, err = model.ProductAttachmentRelationships(mods...).Count(ctx, r.mysqlConn)
		if err != nil {
			return
		}

		if withCount == 0 {
			return
		}
	}

	if pageSize > 0 {
		if page > 1 {
			mods = append(mods, qm.Offset(pageSize*(page-1)))
		}
		mods = append(mods, qm.Limit(pageSize))
	}

	var columns []string
	if selectCol != "" {
		if selectCol != "," {
			columns = strings.Split(selectCol, ",")
		}
		columns = append(columns, model.ProductAttachmentRelationshipColumns.AttachmentRelationshipID)
		columns = append(columns, model.ProductAttachmentRelationshipColumns.ObjectID)
	}
	if len(columns) > 0 {
		mods = append(mods, qm.Select(columns...))
	}

	list, err = model.ProductAttachmentRelationships(mods...).All(ctx, r.mysqlConn)
	return
}
